package org.breathe.command.impl.string;

import lombok.extern.slf4j.Slf4j;
import org.breathe.command.Command;
import org.breathe.command.CommandType;
import org.breathe.datastruct.RedisBytes;
import org.breathe.datastruct.RedisData;
import org.breathe.datastruct.impl.RedisString;
import org.breathe.internal.Sds;
import org.breathe.protocal.Resp;
import org.breathe.protocal.decode.BulkString;
import org.breathe.protocal.decode.Errors;
import org.breathe.protocal.decode.SimpleString;
import org.breathe.server.core.RedisCore;

/**
 * @author: breathe
 * @createTime: 2025-05-12
 */
@Slf4j
public class Set implements Command {
    private RedisBytes key;
    private RedisBytes value;
    private final RedisCore redisCore;
    private long timeout = -1;
    private boolean notExistSet = false;
    private boolean existSet = false;
    public Set(RedisCore redisCore) {
        this.redisCore = redisCore;
    }
    @Override
    public CommandType getType() {
        return CommandType.SET;
    }

    @Override
    public void setContext(Resp[] array) {
        if (array.length < 3) {
            throw new IllegalArgumentException("argue not enough");
        }
        key = ((BulkString)array[1]).getContent();
        value = ((BulkString)array[2]).getContent();
        int idx = 3;
        while (idx < array.length) {
            if (!(array[idx] instanceof BulkString)) {
                throw new IllegalArgumentException("invalid set command option format");
            }
            String option = ((BulkString)array[idx]).getContent().getString().toUpperCase();
            idx ++;
            String argument = null;
            switch (option) {
                case "EX":
                    if (idx >= array.length || !(array[idx] instanceof BulkString)) {
                        throw new IllegalArgumentException("EX command require a numeric argument");
                    }
                    argument = ((BulkString) array[idx]).getContent().getString();
                    try {
                        timeout = Long.parseLong(argument) * 1000;
                        idx ++;
                    } catch (NumberFormatException e) {
                        throw new IllegalArgumentException("EX option value must be integer");
                    }
                    break;
                case "PX":
                    if (idx >= array.length || !(array[idx] instanceof BulkString)) {
                        throw new IllegalArgumentException("PX option requires a numeric argument");
                    }
                    argument = ((BulkString) array[idx]).getContent().getString();
                    try {
                        timeout = Long.parseLong(argument);
                        idx ++;
                    } catch (NumberFormatException e) {
                        throw new IllegalArgumentException("PX option value must be integer");
                    }
                    break;
                case "NX":
                    notExistSet = true;
                    break;
                case "XX":
                    existSet = true;
                    break;
                default:
                    throw new IllegalArgumentException("invalid set option: " + option);
            }
        }
        if (notExistSet && existSet) {
            throw new IllegalArgumentException("NX and XX options are mutually exclusive");
        }
    }

    @Override
    public Resp handle() {
        try {
            RedisData data = null;
            //exist
            if ((data = redisCore.get(key)) != null) {
                if (notExistSet) {
                    return new BulkString(key + " has existed");
                }
                if (data instanceof RedisString redisString) {
                    redisString.setValue(new Sds(value.getBytes()));
                    if (timeout != -1) {
                        redisString.setTimeout(timeout + System.currentTimeMillis());
                    }
                }
            } else {
                //notExist
                if (existSet) {
                    return new BulkString(key + "not exist");
                }
                RedisString redisString = new RedisString(new Sds(value.getBytes()));
                if (timeout != -1) {
                    redisString.setTimeout(timeout + System.currentTimeMillis());
                }
                redisCore.put(key, redisString);
                log.info("key: {} value: {}", key, value);
            }
            return new SimpleString("OK");
        } catch (Exception e) {
            log.error("set command run fail");
            return new Errors("ERR " + e.getMessage());
        }
    }

    @Override
    public boolean writeCommand() {
        return true;
    }
}
